import json

import coin.base.database.util as db_util
import coin.proto.exchange_status_pb2 as checker_proto
from coin.support.exchange_status.logic.db_connector import \
  ExchangeStatusConnector
from coin.util.protobuf.util import proto_to_json


def _int_or_none(val):
  return int(val) if val is not None else val


class ExchangeStatusImporter(ExchangeStatusConnector):
  def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)

  def _insert_obj_to_db(self, obj_list: list):
    session = self._connection
    try:
      session.add_all(obj_list)
      session.commit()
    except Exception as e:
      self._logger.error('Fail to insert status to db. %s %s %s', obj_list, type(e), e)
      session.rollback()

  def insert_rest_status_history(self, request_list, response_list):
    request_type = checker_proto.REST_REQUEST
    status_obj_list = self._gen_status_object_list(request_list,
                                                   response_list,
                                                   request_type,
                                                   self._gen_rest_status_object)
    self._insert_obj_to_db(status_obj_list)

  def insert_feed_status_history(self, request_list, response_list):
    request_type = checker_proto.FEED_REQUEST
    status_obj_list = self._gen_status_object_list(request_list,
                                                   response_list,
                                                   request_type,
                                                   self._gen_feed_status_object)
    self._insert_obj_to_db(status_obj_list)

  def insert_order_status_history(self, request_list, response_list):
    request_type = checker_proto.ORDER_REQUEST
    status_obj_list = self._gen_status_object_list(request_list,
                                                   response_list,
                                                   request_type,
                                                   self._gen_order_status_object)
    self._insert_obj_to_db(status_obj_list)

  def insert_exchange_status_history(self, request_list, response_list):
    request_type = checker_proto.ORDER_REQUEST
    status_obj_list = self._gen_status_object_list(request_list,
                                                   response_list,
                                                   request_type,
                                                   self._gen_exchange_status_object)
    self._insert_obj_to_db(status_obj_list)

  def _gen_status_object_list(self, request_list, response_list, request_type, gen_orm_obj_func):
    status_obj_list = []
    assert len(request_list) == len(response_list)
    for request, response in zip(request_list, response_list):
      try:
        assert request.type == request_type
        self.validate_status_request(request)
        status_obj = gen_orm_obj_func(request, response)
        status_obj_list.append(status_obj)
      except Exception as e:
        self._logger.error('Fail to generate status object. %s %s %s', request, type(e), e)
        continue
    return status_obj_list

  def _gen_rest_status_object(self, request, response):
    response_json_str = proto_to_json(response)
    response = json.loads(response_json_str)
    result = self._RestStatus(exchange_available=response.get('exchange_available', None),
                              ticker_available=response.get('ticker_available', None),
                              balance_available=response.get('balance_available', None),
                              order_available=response.get('order_available', None),
                              submit_available=response.get('submit_available', None),
                              submit_batch_available=response.get('submit_batch_available', None),
                              cancel_available=response.get('cancel_available', None),
                              cancel_all_available=response.get('cancel_all_available', None),
                              rest_status_response=response_json_str)
    result = self.merge_status_request(request, result)
    return result

  def _gen_feed_status_object(self, request, response):
    response_json_str = proto_to_json(response)
    response = json.loads(response_json_str)
    last_feed_received_timestamp = _int_or_none(response.get('last_feed_received_timestamp', None))
    last_feed_changed_timestamp = _int_or_none(response.get('last_feed_changed_timestamp', None))
    last_valid_feed_timestamp = _int_or_none(response.get('last_valid_feed_timestamp', None))
    result = self._FeedStatus(feed_available=response.get('feed_available', None),
                              feed_valid=response.get('feed_valid', None),
                              last_feed_received_timestamp=last_feed_received_timestamp,
                              last_feed_changed_timestamp=last_feed_changed_timestamp,
                              last_valid_feed_timestamp=last_valid_feed_timestamp,
                              feed_status_response=response_json_str)
    result = self.merge_status_request(request, result)
    return result

  def _gen_order_status_object(self, request, response):
    response_json_str = proto_to_json(response)
    response = json.loads(response_json_str)
    last_submit_timestamp = _int_or_none(response.get('last_submit_timestamp', None))
    last_submit_accepted_timestamp = _int_or_none(
        response.get('last_submit_accepted_timestamp', None))
    last_cancel_timestamp = _int_or_none(response.get('last_cancel_timestamp', None))
    last_cancel_accepted_timestamp = _int_or_none(
        response.get('last_cancel_accepted_timestamp', None))
    last_fill_timestamp = _int_or_none(response.get('last_fill_timestamp', None))
    result = self._OrderStatus(order_available=response.get('order_available', None),
                               last_submit_timestamp=last_submit_timestamp,
                               last_submit_accepted_timestamp=last_submit_accepted_timestamp,
                               last_cancel_timestamp=last_cancel_timestamp,
                               last_cancel_accepted_timestamp=last_cancel_accepted_timestamp,
                               last_fill_timestamp=last_fill_timestamp,
                               last_market_order_fill_delay=response.get(
                                   'last_market_order_fill_delay', None),
                               order_status_response=response_json_str)
    result = self.merge_status_request(request, result)
    return result

  def _gen_exchange_status_object(self, request, response):
    response_json_str = proto_to_json(response)
    response = json.loads(response_json_str)
    rest_status_hid = _int_or_none(response.get('rest_status_hid', None))
    feed_status_hid = _int_or_none(response.get('feed_status_hid', None))
    order_status_hid = _int_or_none(response.get('order_status_hid', None))
    result = self._ExchangeStatus(exchange_available=response.get('exchange_available', None),
                                  feed_available=response.get('feed_available', None),
                                  feed_valid=response.get('feed_valid', None),
                                  order_available=response.get('order_available', None),
                                  rest_status_hid=rest_status_hid,
                                  feed_status_hid=feed_status_hid,
                                  order_status_hid=order_status_hid)
    result = self.merge_status_request(request, result)
    return result

  @staticmethod
  def validate_status_request(request):
    assert request.HasField('exchange_api_id')
    assert request.HasField('symbol')
    assert request.HasField('hostname')
    assert request.HasField('query_timestamp')
    if request.type == checker_proto.FEED_REQUEST:
      assert request.HasField('feed_name')
    else:
      assert request.HasField('account_id')

  @staticmethod
  def merge_status_request(request, out):
    query_timestamp = db_util.convert_time_to_timestamp(request.query_timestamp / (10.**9))
    out.exchange_api_id = request.exchange_api_id
    out.symbol = request.symbol
    out.hostname = request.hostname
    out.query_timestamp = query_timestamp
    if request.type == checker_proto.FEED_REQUEST:
      out.feed_name = request.feed_name
    else:
      out.account_id = request.account_id
    return out
